library(ggplot2)
Introduction
In this tutorial you will be introduced to r5r: an interface to the R5 routing engine developed by
Conveyal. R5 (r5r) allows you to do “rapid, realistic” routing on
multimodal networks. It’s very flexible, enabling intermodal analyses
and has the key strength of retaining the full detail of the public
transport timetable. If you’re more familiar with python, you can
consider using r5py instead -
although it may not have all of the same functionality.
As an example, we’re going to be analyzing accessibility by public
transport to beer gardens 🚇🍻
The tutorial consists of the following main steps
- Installing r5r
- GTFS preparation
- Loading spatial data into R
- Creating an Isochrone
In addition to r5r, you will be exposed to several other useful R
packages: dplyr (for working
with data tables), sf (for
working with geodata), and tidytransit (for
working with GTFS data). These are all very powerful packages that can
make your life a lot easier if you learn how to use them! You’re
encouraged to look into the documentation of these packages for more
information.
Installing r5r
All the packages listed below (including r5r) can be
installed by clicking “packages > install” on the sidebar and
searching for the package name or by executing the following command:
install.packages("package_name_here")

Since R5 (the routing engine itself) is written in java, we need to
install the Java Development Kit to make r5r work.
Specifically we need version 21. You can install it here: https://www.oracle.com/de/java/technologies/downloads/#java21
With that done, we can load all the packages we’ll be using
today.
library(tidyverse)
Warning: package ‘tidyverse’ was built under R version 4.1.3Warning: package ‘tibble’ was built under R version 4.1.3Warning: package ‘tidyr’ was built under R version 4.1.3Warning: package ‘forcats’ was built under R version 4.1.3-- Attaching core tidyverse packages ------------------------------------------------------------ tidyverse 2.0.0 --
v dplyr 1.1.4 v readr 2.1.4
v forcats 1.0.0 v stringr 1.5.1
v ggplot2 3.5.1 v tibble 3.2.1
v lubridate 1.9.4 v tidyr 1.3.0
v purrr 1.0.4 -- Conflicts ------------------------------------------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
i Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(sf)
Linking to GEOS 3.10.2, GDAL 3.4.1, PROJ 7.2.1; sf_use_s2() is TRUE
library(tidytransit)
options(java.parameters = '-Xmx4G') # we need to allocate RAM for r5r to work. You can increase this depending on your system resources.
library(r5r)
Please make sure you have already allocated some memory to Java by running:
options(java.parameters = '-Xmx2G').
You should replace '2G' by the amount of memory you'll require. Currently, Java memory is set to -Xmx4G
GTFS Preparation
- Download data from: https://gtfs.de/ - pick “Deutschland gesamt”
- Move the zip file into the “raw_data” folder
- Using the full feed for all of Germany in r5r will require a lot of
resources. Since we’ll be analyzing a single city - lets filter the feed
so that it is smaller. We’ll be using
tidytransit for
this.
- Let’s first load in geodata representing the area we’ll be trimming
to (in this case - Munich). We’ll use
st_read from the
sf package for this. It can handle all typical geospatial
file types (.shp,.gpkg,etc.)
area <- st_read("./raw_data/munich_admin.gpkg")
Reading layer `boundary_administrative_munich' from data source
`C:\Users\barte\Documents\R\ilutm-r5r-tutorial\raw_data\munich_admin.gpkg' using driver `GPKG'
Simple feature collection with 25 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: 11.36078 ymin: 48.06162 xmax: 11.72291 ymax: 48.24812
Geodetic CRS: WGS 84
- It’s good practice to buffer our study area to avoid any edge
effects. Let’s buffer our area by 5 km. NOTE: in this analysis we’re
just going to be looking at accessibility to beer gardens in Munich. We
aren’t considering any that are in the municipalities around the city.
If your destinations extend beyond the border of your city, you may need
to buffer your study area significantly! (>>5 km)
- We’ve seen how to do this in QGIS, let’s use
sf this
time
area_buffered <- st_transform(area,crs = 25832) # switch crs to projected (ETRS89 / UTM zone 32N)
area_buffered <- st_union(area_buffered) # join geometries
area_buffered <- st_buffer(area_buffered, 5000) # buffer by 5km
area_buffered <- st_transform(area_buffered,crs = 4326) #switch back to WGS84 for filtering the GTFS feed
ggplot()+
geom_sf(data = area_buffered)+
geom_sf(data = area)

7.Let’s load in the GTFS feed we downloaded and trim it. We can see
that the number of stops in the feed has been significantly reduced.
- Let’s save the filtered feed. R5 will use this to build a network,
you can also use it for other public transport analyses (refer to
previous tutorials). Tip:
tidytransit has a lot of great
functionality for this!
write_gtfs(gtfs_filtered,"./r5r_model/gtfs.zip")
r5r Set Up
- Before running any more code we need to supply the correct data. So
far we have prepared the GTFS data, but we also need a street network.
R5 uses an extract of OSM data to build the street network. To do this,
it needs to be provided with a
.pbf file. This can be found
on Geofabrik or https://slice.openstreetmap.us/. OSM by the slice is
recommended because it has a cool name and because you can select the
exact area you’re interested in (reducing network size, improving
performance).
- Just as when we were trimming the GTFS feed, extract an area larger
than the city itself.

Save the .pbf file to the /r5r_model/
folder. The folder should look like this: 
We can now tell r5 to build the network. Tip: if you are sure you
aren’t modifying the GTFS or .pbf data, you can turn
overwrite = F to avoid rebuilding the model
(faster).
r5r_core <- setup_r5(data_path = "./r5r_model", verbose = FALSE,overwrite = T)
No raster .tif files found. Using elevation = 'NONE'.
Finished building network.dat at ./r5r_model/network.dat
OD Data
We need some data to use for origins and destinations. Our
destinations will be beer gardens. The data is provided for you. It was
prepared using an Overpass API query (as in previous tutorials).
beergardens <- st_read("raw_data/biergarten.gpkg")
Reading layer `centroids' from data source `C:\Users\barte\Documents\R\ilutm-r5r-tutorial\raw_data\biergarten.gpkg' using driver `GPKG'
Simple feature collection with 100 features and 3 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 11.3911 ymin: 48.07886 xmax: 11.71046 ymax: 48.21359
Geodetic CRS: WGS 84
ggplot()+
geom_sf(data = area)+
geom_sf(data = beergardens)

For our origins, we’ll use public transport stops. As we discussed in
lecture, a single “stop” may be represented by multiple in a GTFS feed
(e.g., a bus stop on both sides of the street). We can take some steps
to simplify this. We will group all stops that share the same value for
parent_station and then keep a single coordinate by taking
the average. Be aware that solving this in a line of code is often too
good to be true. GTFS data often contains mistakes, and may require more
careful cleaning!
stops <- summarize(group_by(gtfs_filtered$stops, parent_station),
stop_lat = mean(stop_lat),
stop_lon = mean(stop_lon),
parent_station_name = first(stop_name))
stops
As a final step lets convert the table to geodata and filter it down
to our study area.
stops <- st_as_sf(stops,coords = c("stop_lon","stop_lat"),crs = 4326,remove = F)
stops <-st_filter(stops,area) #apply spatial filter
stops
Simple feature collection with 1125 features and 4 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS: WGS 84
ggplot()+
geom_sf(data = area)+
geom_sf(data = stops, color = "blue")+
geom_sf(data = beergardens, size = 2)

beergardens
Simple feature collection with 100 features and 4 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 11.3911 ymin: 48.07886 xmax: 11.71046 ymax: 48.21359
Geodetic CRS: WGS 84
First 10 features:
osm_id amenity name geom id
1 110892783 biergarten Lindengarten Biergarten POINT (11.67838 48.11507) 110892783
2 368977873 biergarten Tivoli Pavillon POINT (11.60341 48.16053) 368977873
3 502190647 biergarten Olympia-Alm POINT (11.55432 48.17114) 502190647
4 292910957 biergarten Tambosi Hofgarten POINT (11.5786 48.14293) 292910957
5 1371548072 biergarten Zum Biereder POINT (11.54993 48.16336) 1371548072
6 72273510 biergarten Hirschau POINT (11.60248 48.16209) 72273510
7 343096349 biergarten Paulanergarten POINT (11.5718 48.20859) 343096349
8 325104410 biergarten Ristorante Dolce Vita POINT (11.52478 48.08895) 325104410
9 2907073039 biergarten Zur Gartenlaube POINT (11.60551 48.10014) 2907073039
10 1329567304 biergarten La Locandiera POINT (11.6409 48.08744) 1329567304
–> Before we start using r5r, lets format our data. The origins
and destinations should have an id column.
stops already has this, but beergardens does
not. Let’s add a new column called id, and set the values
to equal osm_id.
beergardens <- mutate(beergardens, id = osm_id)
stops <- mutate(stops, id = parent_station)
beergardens
Simple feature collection with 100 features and 4 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 11.3911 ymin: 48.07886 xmax: 11.71046 ymax: 48.21359
Geodetic CRS: WGS 84
First 10 features:
osm_id amenity name geom id
1 110892783 biergarten Lindengarten Biergarten POINT (11.67838 48.11507) 110892783
2 368977873 biergarten Tivoli Pavillon POINT (11.60341 48.16053) 368977873
3 502190647 biergarten Olympia-Alm POINT (11.55432 48.17114) 502190647
4 292910957 biergarten Tambosi Hofgarten POINT (11.5786 48.14293) 292910957
5 1371548072 biergarten Zum Biereder POINT (11.54993 48.16336) 1371548072
6 72273510 biergarten Hirschau POINT (11.60248 48.16209) 72273510
7 343096349 biergarten Paulanergarten POINT (11.5718 48.20859) 343096349
8 325104410 biergarten Ristorante Dolce Vita POINT (11.52478 48.08895) 325104410
9 2907073039 biergarten Zur Gartenlaube POINT (11.60551 48.10014) 2907073039
10 1329567304 biergarten La Locandiera POINT (11.6409 48.08744) 1329567304
TTM<-
travel_time_matrix(
r5r_core,
origins = stops,
destinations = stops,
mode = c("TRANSIT"),
mode_egress = "WALK",
departure_datetime = as.POSIXct("26-05-2025 9:00:00", format = "%d-%m-%Y %H:%M:%S"), #its important that the date and time is within your GTFS feed
time_window = 10L, #
percentiles = c(10,50),
max_walk_time = 10,
max_trip_duration = 30L,
walk_speed = 4,
max_rides = 3,
)
TTM
Travel Time Matrix
The main functionality of r5r is the ability to calculate a travel
time matrix. Knowing the travel time between pairs of points is useful
on its own. It can also be used if you want more control over your
accessibility calculations. For example, if you calculate a travel time
matrix between pairs of public transport stops, you can calculate the
access (origin –> first public transport stop) and egress (last
public transport stop –> destination) legs of the trip independently.
In other words, combining the output of r5r with the QGIS-based
accessibility calculations we did in earlier tutorials.
Let’s give it a shot:
tip: there are many other settings you can change, you’re encouraged
to read the documentation of r5r to see what is possible! The documentation has many nice
examples!
stops: our origins are the public transport stops
destinations: our destinations are the public transport
stops
mode: the primary mode is public transport (which
includes walking for access and transfers)
mode_egress: the egress mode is walking
departure_datetime: the time trips start
time_window: set to 60. A travel time matrix will be
calculated for each minute, for 60 minutes after the departure time.
This allows for us to account for the variability in travel times.
Imagine we ran our analysis only at 8:00 and we just missed a departure
of an S-Bahn at 7:59, the travel time that we’d calculate would be
severly penalized by excessive waiting. This parameter can be reduced
(default is 10) as it can be computationally expensive. It’s
increasingly important in suburban and rural areas where service
frequencies are low.
percentiles: this corresponds to the
time_window. a value of 50 will report the median travel
time. We can specify multiple percentiles by using a vector
c()
max_walk_time: max time for access, egress, and
transfers by walking
walk_speed: (kph)
max_ride: maximum # of transfers
TTM <- mutate(TTM,tt_diff = travel_time_p50 - travel_time_p10)
print(paste0(round(sum(is.na(TTM$tt_diff)) / length(TTM$tt_diff)*100,0),"%"))
[1] "30%"
The impact of the time_frame can be quite significant! The percent of
OD pairs that had a 50th percentile travel time > 30 min and a 10th
percentile travel time <30 min:
TTM$tt_diff%>%na.omit()%>%summary()
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 2.000 3.000 3.127 4.000 21.000
TTM<-TTM%>%select(-tt_diff)
Those that could be reached, were this much slower:
TTM
Simple feature collection with 294664 features and 5 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS: WGS 84
First 10 features:
from_id to_id travel_time_p10 travel_time_p50 parent_station_name geometry
1 100060 100060 0 0 Josef-Lang-Straße POINT (11.47202 48.15011)
2 100060 10157 29 NA Josef-Lang-Straße POINT (11.46016 48.18474)
3 100060 105596 28 NA Josef-Lang-Straße POINT (11.48913 48.18399)
4 100060 109574 23 27 Josef-Lang-Straße POINT (11.47472 48.12805)
5 100060 11520 25 27 Josef-Lang-Straße POINT (11.54544 48.14428)
6 100060 128167 22 26 Josef-Lang-Straße POINT (11.46642 48.19177)
7 100060 131549 21 25 Josef-Lang-Straße POINT (11.44335 48.16361)
8 100060 131638 21 25 Josef-Lang-Straße POINT (11.47278 48.17485)
9 100060 132365 19 23 Josef-Lang-Straße POINT (11.4531 48.14563)
10 100060 1335 27 NA Josef-Lang-Straße POINT (11.45716 48.20257)
Let’s get the travel time matrix ready for an export. We will
restructure the data so that the geometry of the destination stops
(to_id) is added to each row. We will also add the name of
the origin station so that the data is easier to work with. This is a
similar operation to a VLOOKUP in excel. We use the from_id
and to_id fields as they represent the
parent_station.
For more guidance on dplyr functions (like
left_join()) check out this cheat
sheet

This format is easier to interpret if we take a single stop. For
example, lets find all stops that can be reached from Theresienstraße
within a median travel time of 20 minutes.
st_write(TTM,"./output/TTM.gpkg",append = F)
Writing layer `TTM' to data source `./output/TTM.gpkg' using driver `GPKG'
Writing 294664 features with 5 fields and geometry type Point.
Let’s export this so that we can work with it in QGIS.
st_write(TTM,"./output/TTM.gpkg",append = F)
We can achieve the same result as above by doing the following:

Then, by using what you’ve learned in previous tutorials, you can
create service areas:

LS0tDQp0aXRsZTogIlI1UiBUdXRvcmlhbCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQotLS0NCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KDQojIEludHJvZHVjdGlvbg0KDQpJbiB0aGlzIHR1dG9yaWFsIHlvdSB3aWxsIGJlIGludHJvZHVjZWQgdG8gW3I1cl0oaHR0cHM6Ly9pcGVhZ2l0LmdpdGh1Yi5pby9yNXIvKTogYW4gaW50ZXJmYWNlIHRvIHRoZSBbUjVdKGh0dHBzOi8vZ2l0aHViLmNvbS9jb252ZXlhbC9yNSkgcm91dGluZyBlbmdpbmUgZGV2ZWxvcGVkIGJ5IENvbnZleWFsLg0KUjUgKHI1cikgYWxsb3dzIHlvdSB0byBkbyAicmFwaWQsIHJlYWxpc3RpYyIgcm91dGluZyBvbiBtdWx0aW1vZGFsIG5ldHdvcmtzLiBJdCdzIHZlcnkgZmxleGlibGUsIGVuYWJsaW5nDQppbnRlcm1vZGFsIGFuYWx5c2VzIGFuZCBoYXMgdGhlIGtleSBzdHJlbmd0aCBvZiByZXRhaW5pbmcgdGhlIGZ1bGwgZGV0YWlsIG9mIHRoZSBwdWJsaWMgdHJhbnNwb3J0IHRpbWV0YWJsZS4NCklmIHlvdSdyZSBtb3JlIGZhbWlsaWFyIHdpdGggcHl0aG9uLCB5b3UgY2FuIGNvbnNpZGVyIHVzaW5nIFtyNXB5XShodHRwczovL2dpdGh1Yi5jb20vcjVweS9yNXB5KSBpbnN0ZWFkIC0gYWx0aG91Z2ggaXQgbWF5IG5vdCBoYXZlIGFsbCBvZiB0aGUgc2FtZSBmdW5jdGlvbmFsaXR5Lg0KDQpBcyBhbiBleGFtcGxlLCB3ZSdyZSBnb2luZyB0byBiZSBhbmFseXppbmcgYWNjZXNzaWJpbGl0eSBieSBwdWJsaWMgdHJhbnNwb3J0IHRvIGJlZXIgZ2FyZGVucyDwn5qH8J+Nuw0KDQoNClRoZSB0dXRvcmlhbCBjb25zaXN0cyBvZiB0aGUgZm9sbG93aW5nIG1haW4gc3RlcHMNCg0KMS4gSW5zdGFsbGluZyByNXINCjIuIEdURlMgcHJlcGFyYXRpb24NCjMuIExvYWRpbmcgc3BhdGlhbCBkYXRhIGludG8gUg0KNC4gQ3JlYXRpbmcgYW4gSXNvY2hyb25lDQoNCg0KSW4gYWRkaXRpb24gdG8gcjVyLCB5b3Ugd2lsbCBiZSBleHBvc2VkIHRvIHNldmVyYWwgb3RoZXIgdXNlZnVsIFIgcGFja2FnZXM6IFtkcGx5cl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLykgKGZvciB3b3JraW5nIHdpdGggZGF0YSB0YWJsZXMpLCBbc2ZdKGh0dHBzOi8vci1zcGF0aWFsLmdpdGh1Yi5pby9zZi8pIChmb3Igd29ya2luZyB3aXRoIGdlb2RhdGEpLCBhbmQgW3RpZHl0cmFuc2l0XShodHRwczovL2dpdGh1Yi5jb20vci10cmFuc2l0L3RpZHl0cmFuc2l0KSAoZm9yIHdvcmtpbmcgd2l0aCBHVEZTIGRhdGEpLiBUaGVzZSBhcmUgYWxsIHZlcnkNCnBvd2VyZnVsIHBhY2thZ2VzIHRoYXQgY2FuIG1ha2UgeW91ciBsaWZlIGEgbG90IGVhc2llciBpZiB5b3UgbGVhcm4gaG93IHRvIHVzZSB0aGVtISBZb3UncmUgZW5jb3VyYWdlZCANCnRvIGxvb2sgaW50byB0aGUgZG9jdW1lbnRhdGlvbiBvZiB0aGVzZSBwYWNrYWdlcyBmb3IgbW9yZSBpbmZvcm1hdGlvbi4NCg0KDQojIEluc3RhbGxpbmcgYHI1cmANCg0KQWxsIHRoZSBwYWNrYWdlcyBsaXN0ZWQgYmVsb3cgKGluY2x1ZGluZyBgcjVyYCkgY2FuIGJlIGluc3RhbGxlZCBieSBjbGlja2luZyAicGFja2FnZXMgPiBpbnN0YWxsIg0Kb24gdGhlIHNpZGViYXIgYW5kIHNlYXJjaGluZyBmb3IgdGhlIHBhY2thZ2UgbmFtZSBvciBieSBleGVjdXRpbmcgdGhlIGZvbGxvd2luZyBjb21tYW5kOiBgaW5zdGFsbC5wYWNrYWdlcygicGFja2FnZV9uYW1lX2hlcmUiKWANCg0KIVtdKC4vaW1nL2luc3RhbGxfcGFja2FnZXMucG5nKQ0KDQoNClNpbmNlIFI1ICh0aGUgcm91dGluZyBlbmdpbmUgaXRzZWxmKSBpcyB3cml0dGVuIGluIGphdmEsIHdlIG5lZWQgdG8gaW5zdGFsbCB0aGUgSmF2YSBEZXZlbG9wbWVudCBLaXQNCnRvIG1ha2UgYHI1cmAgd29yay4gU3BlY2lmaWNhbGx5IHdlIG5lZWQgdmVyc2lvbiAyMS4gWW91IGNhbiBpbnN0YWxsIGl0IGhlcmU6IGh0dHBzOi8vd3d3Lm9yYWNsZS5jb20vZGUvamF2YS90ZWNobm9sb2dpZXMvZG93bmxvYWRzLyNqYXZhMjENCg0KDQpXaXRoIHRoYXQgZG9uZSwgd2UgY2FuIGxvYWQgYWxsIHRoZSBwYWNrYWdlcyB3ZSdsbCBiZSB1c2luZyB0b2RheS4NCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeSh0aWR5dHJhbnNpdCkNCg0Kb3B0aW9ucyhqYXZhLnBhcmFtZXRlcnMgPSAnLVhteDRHJykgIyB3ZSBuZWVkIHRvIGFsbG9jYXRlIFJBTSBmb3IgcjVyIHRvIHdvcmsuIFlvdSBjYW4gaW5jcmVhc2UgdGhpcyBkZXBlbmRpbmcgb24geW91ciBzeXN0ZW0gcmVzb3VyY2VzLg0KbGlicmFyeShyNXIpDQpgYGANCg0KDQojIEdURlMgUHJlcGFyYXRpb24NCg0KMS4gRG93bmxvYWQgZGF0YSBmcm9tOiBodHRwczovL2d0ZnMuZGUvIC0gcGljayAiRGV1dHNjaGxhbmQgZ2VzYW10Ig0KMi4gTW92ZSB0aGUgemlwIGZpbGUgaW50byB0aGUgInJhd19kYXRhIiBmb2xkZXINCjMuIFVzaW5nIHRoZSBmdWxsIGZlZWQgZm9yIGFsbCBvZiBHZXJtYW55IGluIHI1ciB3aWxsIHJlcXVpcmUgYSBsb3Qgb2YgcmVzb3VyY2VzLiBTaW5jZSB3ZSdsbCBiZSANCmFuYWx5emluZyBhIHNpbmdsZSBjaXR5IC0gbGV0cyBmaWx0ZXIgdGhlIGZlZWQgc28gdGhhdCBpdCBpcyBzbWFsbGVyLiBXZSdsbCBiZSB1c2luZyBgdGlkeXRyYW5zaXRgIGZvciB0aGlzLg0KNC4gTGV0J3MgZmlyc3QgbG9hZCBpbiBnZW9kYXRhIHJlcHJlc2VudGluZyB0aGUgYXJlYSB3ZSdsbCBiZSB0cmltbWluZyB0byAoaW4gdGhpcyBjYXNlIC0gTXVuaWNoKS4gV2UnbGwgdXNlIGBzdF9yZWFkYCBmcm9tIHRoZSBgc2ZgIHBhY2thZ2UgZm9yIHRoaXMuIEl0IGNhbiBoYW5kbGUgYWxsIHR5cGljYWwgZ2Vvc3BhdGlhbCBmaWxlIHR5cGVzIChgLnNocGAsYC5ncGtnYCxldGMuKQ0KDQpgYGB7cn0NCmFyZWEgPC0gc3RfcmVhZCgiLi9yYXdfZGF0YS9tdW5pY2hfYWRtaW4uZ3BrZyIpDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgID0gYXJlYSkNCg0KYGBgDQo1LiBJdCdzIGdvb2QgcHJhY3RpY2UgdG8gYnVmZmVyIG91ciBzdHVkeSBhcmVhIHRvIGF2b2lkIGFueSBlZGdlIGVmZmVjdHMuIExldCdzIGJ1ZmZlciBvdXIgYXJlYSBieSA1IGttLiBOT1RFOiBpbiB0aGlzIGFuYWx5c2lzIHdlJ3JlIGp1c3QgZ29pbmcgdG8gYmUgbG9va2luZyBhdCBhY2Nlc3NpYmlsaXR5IHRvIGJlZXIgZ2FyZGVucyBpbiBNdW5pY2guIFdlIGFyZW4ndCBjb25zaWRlcmluZyBhbnkgdGhhdCBhcmUgaW4gdGhlIG11bmljaXBhbGl0aWVzIGFyb3VuZCB0aGUgY2l0eS4gSWYgeW91ciBkZXN0aW5hdGlvbnMgZXh0ZW5kIGJleW9uZCB0aGUgYm9yZGVyIG9mIHlvdXIgY2l0eSwgeW91IG1heSBuZWVkIHRvIGJ1ZmZlciB5b3VyIHN0dWR5IGFyZWEgc2lnbmlmaWNhbnRseSEgKD4+NSBrbSkNCjYuIFdlJ3ZlIHNlZW4gaG93IHRvIGRvIHRoaXMgaW4gUUdJUywgbGV0J3MgdXNlIGBzZmAgdGhpcyB0aW1lDQoNCmBgYHtyfQ0KYXJlYV9idWZmZXJlZCA8LSBzdF90cmFuc2Zvcm0oYXJlYSxjcnMgPSAyNTgzMikgIyBzd2l0Y2ggY3JzIHRvIHByb2plY3RlZCAoRVRSUzg5IC8gVVRNIHpvbmUgMzJOKQ0KDQphcmVhX2J1ZmZlcmVkIDwtIHN0X3VuaW9uKGFyZWFfYnVmZmVyZWQpICMgam9pbiBnZW9tZXRyaWVzIA0KDQphcmVhX2J1ZmZlcmVkIDwtIHN0X2J1ZmZlcihhcmVhX2J1ZmZlcmVkLCA1MDAwKSAjIGJ1ZmZlciBieSA1a20NCg0KYXJlYV9idWZmZXJlZCA8LSBzdF90cmFuc2Zvcm0oYXJlYV9idWZmZXJlZCxjcnMgPSA0MzI2KSAjc3dpdGNoIGJhY2sgdG8gV0dTODQgZm9yIGZpbHRlcmluZyB0aGUgR1RGUyBmZWVkDQoNCg0KZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YSA9IGFyZWFfYnVmZmVyZWQpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKQ0KYGBgDQo3LkxldCdzIGxvYWQgaW4gdGhlIEdURlMgZmVlZCB3ZSBkb3dubG9hZGVkIGFuZCB0cmltIGl0LiBXZSBjYW4gc2VlIHRoYXQgdGhlIG51bWJlciBvZiBzdG9wcyBpbiB0aGUgZmVlZCBoYXMgYmVlbiBzaWduaWZpY2FudGx5IHJlZHVjZWQuDQpgYGB7cn0NCg0KZ3Rmc19yYXcgPC0gcmVhZF9ndGZzKCIuL3Jhd19kYXRhL2xhdGVzdC56aXAiLGVuY29kaW5nID0gIlVURi04IikgIyBwYXRoIHNob3VsZCBwb2ludCB0byB0aGUgR1RGUyBmZWVkIHlvdSBkb3dubG9hZGVkDQoNCmd0ZnNfZmlsdGVyZWQgPC0gZmlsdGVyX2ZlZWRfYnlfYXJlYShndGZzX3JhdyxzdF9iYm94KGFyZWFfYnVmZmVyZWQpKQ0KDQoNCg0KdHJpYmJsZSh+bl9zdG9wc19yYXcsIH5uX3N0b3BzX2ZpbHRlcmVkLA0KICAgICAgICBucm93KGd0ZnNfcmF3JHN0b3BzKSwgbnJvdyhndGZzX2ZpbHRlcmVkJHN0b3BzKSkNCmBgYA0KDQoNCjguIExldCdzIHNhdmUgdGhlIGZpbHRlcmVkIGZlZWQuIFI1IHdpbGwgdXNlIHRoaXMgdG8gYnVpbGQgYSBuZXR3b3JrLCB5b3UgY2FuIGFsc28gdXNlIGl0IGZvciBvdGhlciBwdWJsaWMgdHJhbnNwb3J0IGFuYWx5c2VzIA0KKHJlZmVyIHRvIHByZXZpb3VzIHR1dG9yaWFscykuIFRpcDogYHRpZHl0cmFuc2l0YCBoYXMgYSBsb3Qgb2YgZ3JlYXQgZnVuY3Rpb25hbGl0eSBmb3IgdGhpcyENCmBgYHtyfQ0KDQp3cml0ZV9ndGZzKGd0ZnNfZmlsdGVyZWQsIi4vcjVyX21vZGVsL2d0ZnMuemlwIikNCg0KYGBgDQoNCg0KIyBgcjVyYCBTZXQgVXANCg0KMS4gQmVmb3JlIHJ1bm5pbmcgYW55IG1vcmUgY29kZSB3ZSBuZWVkIHRvIHN1cHBseSB0aGUgY29ycmVjdCBkYXRhLiBTbyBmYXIgd2UgaGF2ZSBwcmVwYXJlZCB0aGUgR1RGUyBkYXRhLCBidXQgd2UgYWxzbyBuZWVkIGEgc3RyZWV0IG5ldHdvcmsuIFI1IHVzZXMgYW4gZXh0cmFjdCBvZiBPU00gZGF0YSB0byBidWlsZCB0aGUgc3RyZWV0IG5ldHdvcmsuIFRvIGRvIHRoaXMsIGl0IG5lZWRzIHRvIGJlIHByb3ZpZGVkIHdpdGggYSBgLnBiZmAgZmlsZS4gVGhpcyBjYW4gYmUgZm91bmQgb24gW0dlb2ZhYnJpa10oaHR0cDovL2Rvd25sb2FkLmdlb2ZhYnJpay5kZS8pIG9yIGh0dHBzOi8vc2xpY2Uub3BlbnN0cmVldG1hcC51cy8uIE9TTSBieSB0aGUgc2xpY2UgaXMgcmVjb21tZW5kZWQgYmVjYXVzZSBpdCBoYXMgYSBjb29sIG5hbWUgYW5kIGJlY2F1c2UgeW91IGNhbiBzZWxlY3QgdGhlIGV4YWN0IGFyZWEgeW91J3JlIGludGVyZXN0ZWQgaW4gKHJlZHVjaW5nIG5ldHdvcmsgc2l6ZSwgaW1wcm92aW5nIHBlcmZvcm1hbmNlKS4NCjIuIEp1c3QgYXMgd2hlbiB3ZSB3ZXJlIHRyaW1taW5nIHRoZSBHVEZTIGZlZWQsIGV4dHJhY3QgYW4gYXJlYSBsYXJnZXIgdGhhbiB0aGUgY2l0eSBpdHNlbGYuDQoNCiFbXSguL2ltZy9zbGljZS5wbmcpDQoNCjMuIFNhdmUgdGhlIGAucGJmYCBmaWxlIHRvIHRoZSBgL3I1cl9tb2RlbC9gIGZvbGRlci4gVGhlIGZvbGRlciBzaG91bGQgbG9vayBsaWtlIHRoaXM6DQohW10oLi9pbWcvcjVyX2ZvbGRlci5wbmcpDQoNCjQuIFdlIGNhbiBub3cgdGVsbCByNSB0byBidWlsZCB0aGUgbmV0d29yay4gVGlwOiBpZiB5b3UgYXJlIHN1cmUgeW91IGFyZW4ndCBtb2RpZnlpbmcgdGhlIEdURlMgb3IgYC5wYmZgIGRhdGEsIHlvdSBjYW4gdHVybiBgb3ZlcndyaXRlID0gRmAgdG8gYXZvaWQgcmVidWlsZGluZyB0aGUgbW9kZWwgKGZhc3RlcikuDQoNCmBgYHtyfQ0KcjVyX2NvcmUgPC0gc2V0dXBfcjUoZGF0YV9wYXRoID0gIi4vcjVyX21vZGVsIiwgdmVyYm9zZSA9IEZBTFNFLG92ZXJ3cml0ZSA9IFQpDQpgYGANCiMgT0QgRGF0YQ0KDQpXZSBuZWVkIHNvbWUgZGF0YSB0byB1c2UgZm9yIG9yaWdpbnMgYW5kIGRlc3RpbmF0aW9ucy4gT3VyIGRlc3RpbmF0aW9ucyB3aWxsIGJlIGJlZXIgZ2FyZGVucy4gVGhlIGRhdGEgaXMgcHJvdmlkZWQgZm9yIHlvdS4gSXQgd2FzIHByZXBhcmVkIHVzaW5nIGFuIE92ZXJwYXNzIEFQSSBxdWVyeSAoYXMgaW4gcHJldmlvdXMgdHV0b3JpYWxzKS4NCg0KDQpgYGB7cn0NCmJlZXJnYXJkZW5zIDwtIHN0X3JlYWQoInJhd19kYXRhL2JpZXJnYXJ0ZW4uZ3BrZyIpDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKSsNCiAgZ2VvbV9zZihkYXRhID0gYmVlcmdhcmRlbnMpDQpgYGANCg0KRm9yIG91ciBvcmlnaW5zLCB3ZSdsbCB1c2UgcHVibGljIHRyYW5zcG9ydCBzdG9wcy4gQXMgd2UgZGlzY3Vzc2VkIGluIGxlY3R1cmUsIGEgc2luZ2xlICJzdG9wIiBtYXkgYmUgcmVwcmVzZW50ZWQgYnkgbXVsdGlwbGUgaW4gYSBHVEZTIGZlZWQgKGUuZy4sIGEgYnVzIHN0b3Agb24gYm90aCBzaWRlcyBvZiB0aGUgc3RyZWV0KS4gV2UgY2FuIHRha2Ugc29tZSBzdGVwcyB0byBzaW1wbGlmeSB0aGlzLiBXZSB3aWxsIGdyb3VwIGFsbCBzdG9wcyB0aGF0IHNoYXJlIHRoZSBzYW1lIHZhbHVlIGZvciBgcGFyZW50X3N0YXRpb25gIGFuZCB0aGVuIGtlZXAgYSBzaW5nbGUgY29vcmRpbmF0ZSBieSB0YWtpbmcgdGhlIGF2ZXJhZ2UuIEJlIGF3YXJlIHRoYXQgc29sdmluZyB0aGlzIGluIGEgbGluZSBvZiBjb2RlIGlzIG9mdGVuIHRvbyBnb29kIHRvIGJlIHRydWUuIEdURlMgZGF0YSBvZnRlbiBjb250YWlucyBtaXN0YWtlcywgYW5kIG1heSByZXF1aXJlIG1vcmUgY2FyZWZ1bCBjbGVhbmluZyENCg0KYGBge3J9DQpzdG9wcyA8LSBzdW1tYXJpemUoZ3JvdXBfYnkoZ3Rmc19maWx0ZXJlZCRzdG9wcywgcGFyZW50X3N0YXRpb24pLA0KICAgICAgICAgICAgICAgICAgIHN0b3BfbGF0ID0gbWVhbihzdG9wX2xhdCksDQogICAgICAgICAgICAgICAgICAgc3RvcF9sb24gPSBtZWFuKHN0b3BfbG9uKSwNCiAgICAgICAgICAgICAgICAgICBwYXJlbnRfc3RhdGlvbl9uYW1lID0gZmlyc3Qoc3RvcF9uYW1lKSkgIA0Kc3RvcHMNCmBgYA0KDQpBcyBhIGZpbmFsIHN0ZXAgbGV0cyBjb252ZXJ0IHRoZSB0YWJsZSB0byBnZW9kYXRhIGFuZCBmaWx0ZXIgaXQgZG93biB0byBvdXIgc3R1ZHkgYXJlYS4NCg0KYGBge3J9DQpzdG9wcyA8LSBzdF9hc19zZihzdG9wcyxjb29yZHMgPSBjKCJzdG9wX2xvbiIsInN0b3BfbGF0IiksY3JzID0gNDMyNixyZW1vdmUgPSBGKQ0KDQpzdG9wcyA8LXN0X2ZpbHRlcihzdG9wcyxhcmVhKSAjYXBwbHkgc3BhdGlhbCBmaWx0ZXINCg0Kc3RvcHMNCmBgYA0KDQpgYGB7cn0NCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKSsNCiAgZ2VvbV9zZihkYXRhID0gc3RvcHMsIGNvbG9yID0gImJsdWUiKSsNCiAgZ2VvbV9zZihkYXRhID0gYmVlcmdhcmRlbnMsIHNpemUgPSAyKQ0KYGBgDQo8IS0tDQpBcyB3ZSBjYW4gc2VlLCB0aGlzIGlzIGEgbG90IG9mIHB1YmxpYyB0cmFuc3BvcnQgc3RvcHMhIFdlIGNhbiBjb250aW51ZSBjbGVhbmluZyB0aGlzIHVwIC0gYnV0IGZvciB0aGUgcHVycG9zZSBvZiB0aGUgZXhlcmNpc2UsIGxldCdzIHVzZSBkYXRhIHRoYXQgd2UndmUgdXNlZCBpbiBhIHByZXZpb3VzIHR1dG9yaWFsIChyYWlsIHN0b3BzKS4gVGhlIGJlZXIgZ2FyZGVuIGRhdGEgd2FzIHByb3ZpZGVkIGFzIGEgYC5ncGtnYCB3aGljaCB3ZSB3ZXJlIGFibGUgdG8gcmVhZCB1c2luZyBgc3RfcmVhZCgpYC4gQXMgdGhpcyBkYXRhIGlzIGEgYC5jc3ZgIHdlIHdpbGwgdXNlIGByZWFkX2NzdigpYCBhbmQgdGhlbiBjb252ZXJ0IGl0IGludG8gYSBnZW9kYXRhIGZvcm1hdC4NCg0KYGBge3J9DQpzdG9wcyA8LSByZWFkX2NzdigiLi9yYXdfZGF0YS9NdW5pY2hfU3RhdGlvbnMuY3N2IikNCg0Kc3RvcHMgPC0gc3RfYXNfc2Yoc3RvcHMsY29vcmRzID0gYygibG9uIiwibGF0IiksY3JzID0gNDMyNixyZW1vdmUgPSBGKQ0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gYXJlYSkrDQogIGdlb21fc2YoZGF0YSA9IHN0b3BzLCBjb2xvciA9ICJibHVlIikrDQogIGdlb21fc2YoZGF0YSA9IGJlZXJnYXJkZW5zLCBzaXplID0gMikNCmBgYA0KDQotLT4NCkJlZm9yZSB3ZSBzdGFydCB1c2luZyByNXIsIGxldHMgZm9ybWF0IG91ciBkYXRhLiBUaGUgb3JpZ2lucyBhbmQgZGVzdGluYXRpb25zIHNob3VsZCBoYXZlIGFuIGBpZGAgY29sdW1uLiBgc3RvcHNgIGFscmVhZHkgaGFzIHRoaXMsIGJ1dCBgYmVlcmdhcmRlbnNgIGRvZXMgbm90LiBMZXQncyBhZGQgYSBuZXcgY29sdW1uIGNhbGxlZCBgaWRgLCBhbmQgc2V0IHRoZSB2YWx1ZXMgdG8gZXF1YWwgYG9zbV9pZGAuDQoNCmBgYHtyfQ0KYmVlcmdhcmRlbnMNCmBgYA0KDQoNCmBgYHtyfQ0KYmVlcmdhcmRlbnMgPC0gbXV0YXRlKGJlZXJnYXJkZW5zLCBpZCA9IG9zbV9pZCkNCnN0b3BzIDwtIG11dGF0ZShzdG9wcywgaWQgPSBwYXJlbnRfc3RhdGlvbikNCg0KYmVlcmdhcmRlbnMNCmBgYA0KDQoNCiMgVHJhdmVsIFRpbWUgTWF0cml4DQoNClRoZSBtYWluIGZ1bmN0aW9uYWxpdHkgb2YgcjVyIGlzIHRoZSBhYmlsaXR5IHRvIGNhbGN1bGF0ZSBhIHRyYXZlbCB0aW1lIG1hdHJpeC4gS25vd2luZyB0aGUgdHJhdmVsIHRpbWUgYmV0d2VlbiBwYWlycyBvZiBwb2ludHMgaXMgdXNlZnVsIG9uIGl0cyBvd24uIEl0IGNhbiBhbHNvIGJlIHVzZWQgaWYgeW91IHdhbnQgbW9yZSBjb250cm9sIG92ZXIgeW91ciBhY2Nlc3NpYmlsaXR5IGNhbGN1bGF0aW9ucy4gRm9yIGV4YW1wbGUsIGlmIHlvdSBjYWxjdWxhdGUgYSB0cmF2ZWwgdGltZSBtYXRyaXggYmV0d2VlbiBwYWlycyBvZiBwdWJsaWMgdHJhbnNwb3J0IHN0b3BzLCB5b3UgY2FuIGNhbGN1bGF0ZSB0aGUgYWNjZXNzIChvcmlnaW4gLS0+IGZpcnN0IHB1YmxpYyB0cmFuc3BvcnQgc3RvcCkgYW5kIGVncmVzcyAobGFzdCBwdWJsaWMgdHJhbnNwb3J0IHN0b3AgLS0+IGRlc3RpbmF0aW9uKSBsZWdzIG9mIHRoZSB0cmlwIGluZGVwZW5kZW50bHkuIEluIG90aGVyIHdvcmRzLCBjb21iaW5pbmcgdGhlIG91dHB1dCBvZiByNXIgd2l0aCB0aGUgUUdJUy1iYXNlZCBhY2Nlc3NpYmlsaXR5IGNhbGN1bGF0aW9ucyB3ZSBkaWQgaW4gZWFybGllciB0dXRvcmlhbHMuDQoNCkxldCdzIGdpdmUgaXQgYSBzaG90Og0KDQp0aXA6IHRoZXJlIGFyZSBtYW55IG90aGVyIHNldHRpbmdzIHlvdSBjYW4gY2hhbmdlLCB5b3UncmUgZW5jb3VyYWdlZCB0byByZWFkIHRoZSBkb2N1bWVudGF0aW9uIG9mIHI1ciB0byBzZWUgd2hhdCBpcyBwb3NzaWJsZSEgVGhlIFtkb2N1bWVudGF0aW9uXShodHRwczovL2lwZWFnaXQuZ2l0aHViLmlvL3I1ci8pIGhhcyBtYW55IG5pY2UgZXhhbXBsZXMhDQoNCi0gYHN0b3BzYDogb3VyIG9yaWdpbnMgYXJlIHRoZSBwdWJsaWMgdHJhbnNwb3J0IHN0b3BzDQotIGBkZXN0aW5hdGlvbnNgOiBvdXIgZGVzdGluYXRpb25zIGFyZSB0aGUgcHVibGljIHRyYW5zcG9ydCBzdG9wcw0KLSBgbW9kZWA6IHRoZSBwcmltYXJ5IG1vZGUgaXMgcHVibGljIHRyYW5zcG9ydCAod2hpY2ggaW5jbHVkZXMgd2Fsa2luZyBmb3IgYWNjZXNzIGFuZCB0cmFuc2ZlcnMpDQotIGBtb2RlX2VncmVzc2A6IHRoZSBlZ3Jlc3MgbW9kZSBpcyB3YWxraW5nDQotIGBkZXBhcnR1cmVfZGF0ZXRpbWVgOiB0aGUgdGltZSB0cmlwcyBzdGFydA0KLSBgdGltZV93aW5kb3dgOiBzZXQgdG8gNjAuIEEgdHJhdmVsIHRpbWUgbWF0cml4IHdpbGwgYmUgY2FsY3VsYXRlZCBmb3IgZWFjaCBtaW51dGUsIGZvciA2MCBtaW51dGVzIGFmdGVyIHRoZSBkZXBhcnR1cmUgdGltZS4gVGhpcyBhbGxvd3MgZm9yIHVzIHRvIGFjY291bnQgZm9yIHRoZSB2YXJpYWJpbGl0eSBpbiB0cmF2ZWwgdGltZXMuIEltYWdpbmUgd2UgcmFuIG91ciBhbmFseXNpcyBvbmx5IGF0IDg6MDAgYW5kIHdlIGp1c3QgbWlzc2VkIGEgZGVwYXJ0dXJlIG9mIGFuIFMtQmFobiBhdCA3OjU5LCB0aGUgdHJhdmVsIHRpbWUgdGhhdCB3ZSdkIGNhbGN1bGF0ZSB3b3VsZCBiZSBzZXZlcmx5IHBlbmFsaXplZCBieSBleGNlc3NpdmUgd2FpdGluZy4gVGhpcyBwYXJhbWV0ZXIgY2FuIGJlIHJlZHVjZWQgKGRlZmF1bHQgaXMgMTApIGFzIGl0IGNhbiBiZSBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlLiBJdCdzIGluY3JlYXNpbmdseSBpbXBvcnRhbnQgaW4gc3VidXJiYW4gYW5kIHJ1cmFsIGFyZWFzIHdoZXJlIHNlcnZpY2UgZnJlcXVlbmNpZXMgYXJlIGxvdy4NCi0gYHBlcmNlbnRpbGVzYDogdGhpcyBjb3JyZXNwb25kcyB0byB0aGUgYHRpbWVfd2luZG93YC4gYSB2YWx1ZSBvZiA1MCB3aWxsIHJlcG9ydCB0aGUgbWVkaWFuIHRyYXZlbCB0aW1lLiBXZSBjYW4gc3BlY2lmeSBtdWx0aXBsZSBwZXJjZW50aWxlcyBieSB1c2luZyBhIHZlY3RvciBgYygpYA0KLSBgbWF4X3dhbGtfdGltZWA6IG1heCB0aW1lIGZvciBhY2Nlc3MsIGVncmVzcywgYW5kIHRyYW5zZmVycyBieSB3YWxraW5nDQotIGB3YWxrX3NwZWVkYDogKGtwaCkNCi0gYG1heF9yaWRlYDogbWF4aW11bSAjIG9mIHRyYW5zZmVycw0KDQoNCg0KYGBge3J9DQoNClRUTTwtDQp0cmF2ZWxfdGltZV9tYXRyaXgoDQogIHI1cl9jb3JlLA0KICBvcmlnaW5zID0gc3RvcHMsIA0KICBkZXN0aW5hdGlvbnMgPSBzdG9wcywgDQogIG1vZGUgPSBjKCJUUkFOU0lUIiksICANCiAgbW9kZV9lZ3Jlc3MgPSAiV0FMSyIsIA0KICBkZXBhcnR1cmVfZGF0ZXRpbWUgPSBhcy5QT1NJWGN0KCIyNi0wNS0yMDI1IDk6MDA6MDAiLCBmb3JtYXQgPSAiJWQtJW0tJVkgJUg6JU06JVMiKSwgI2l0cyBpbXBvcnRhbnQgdGhhdCB0aGUgZGF0ZSBhbmQgdGltZSBpcyB3aXRoaW4geW91ciBHVEZTIGZlZWQNCiAgdGltZV93aW5kb3cgPSAxMEwsICMgDQogIHBlcmNlbnRpbGVzID0gYygxMCw1MCksDQogIG1heF93YWxrX3RpbWUgPSAxMCwNCiAgbWF4X3RyaXBfZHVyYXRpb24gPSAzMEwsDQogIHdhbGtfc3BlZWQgPSA0LA0KICBtYXhfcmlkZXMgPSAzLA0KKQ0KDQpUVE0NCmBgYA0KDQpUaGUgaW1wYWN0IG9mIHRoZSB0aW1lX2ZyYW1lIGNhbiBiZSBxdWl0ZSBzaWduaWZpY2FudCEgVGhlIHBlcmNlbnQgb2YgT0QgcGFpcnMgdGhhdCBoYWQgYSA1MHRoIHBlcmNlbnRpbGUgdHJhdmVsIHRpbWUgPiAzMCBtaW4gYW5kIGEgMTB0aCBwZXJjZW50aWxlIHRyYXZlbCB0aW1lIDwzMCBtaW46DQoNCmBgYHtyfQ0KVFRNIDwtIG11dGF0ZShUVE0sdHRfZGlmZiA9IHRyYXZlbF90aW1lX3A1MCAtIHRyYXZlbF90aW1lX3AxMCkNCg0KcHJpbnQocGFzdGUwKHJvdW5kKHN1bShpcy5uYShUVE0kdHRfZGlmZikpIC8gbGVuZ3RoKFRUTSR0dF9kaWZmKSoxMDAsMCksIiUiKSkNCmBgYA0KDQpUaG9zZSB0aGF0IGNvdWxkIGJlIHJlYWNoZWQsIHdlcmUgdGhpcyBtdWNoIHNsb3dlcjoNCmBgYHtyfQ0KVFRNJHR0X2RpZmYlPiVuYS5vbWl0KCklPiVzdW1tYXJ5KCkNCg0KVFRNPC1UVE0lPiVzZWxlY3QoLXR0X2RpZmYpDQpgYGANCkxldCdzIGdldCB0aGUgdHJhdmVsIHRpbWUgbWF0cml4IHJlYWR5IGZvciBhbiBleHBvcnQuIFdlIHdpbGwgcmVzdHJ1Y3R1cmUgdGhlIGRhdGEgc28gdGhhdCB0aGUgZ2VvbWV0cnkgb2YgdGhlIGRlc3RpbmF0aW9uIHN0b3BzIChgdG9faWRgKSBpcyBhZGRlZCB0byBlYWNoIHJvdy4gV2Ugd2lsbCBhbHNvIGFkZCB0aGUgbmFtZSBvZiB0aGUgb3JpZ2luIHN0YXRpb24gc28gdGhhdCB0aGUgZGF0YSBpcyBlYXNpZXIgdG8gd29yayB3aXRoLiBUaGlzIGlzIGEgc2ltaWxhciBvcGVyYXRpb24gdG8gYSBWTE9PS1VQIGluIGV4Y2VsLiBXZSB1c2UgdGhlIGBmcm9tX2lkYCBhbmQgYHRvX2lkYCBmaWVsZHMgYXMgdGhleSByZXByZXNlbnQgdGhlIGBwYXJlbnRfc3RhdGlvbmAuIA0KDQoNCkZvciBtb3JlIGd1aWRhbmNlIG9uIGBkcGx5cmAgZnVuY3Rpb25zIChsaWtlIGBsZWZ0X2pvaW4oKWApIGNoZWNrIG91dCB0aGlzIFtjaGVhdCBzaGVldF0oaHR0cHM6Ly9ueXUtY2RzYy5naXRodWIuaW8vbGVhcm5pbmdyL2Fzc2V0cy9kYXRhLXRyYW5zZm9ybWF0aW9uLnBkZikNCg0KYGBge3J9DQojIGpvaW4gdGhlIGdlb21ldHJ5IG9mIHRoZSBkZXN0aW5hdGlvbiBzdGF0aW9uDQpnZW9tZXRyeV90b19qb2luIDwtIHNlbGVjdChzdG9wcyxpZCkgIyBkcm9wIHVubmVjZXNzYXJ5IGNvbHMNCmdlb21ldHJ5X3RvX2pvaW4gPC0gcmVuYW1lKGdlb21ldHJ5X3RvX2pvaW4sdG9faWQgPSAiaWQiKSAjIHJlbmFtZSAiaWQiIHRvICJ0b19pZCIgc28gdGhhdCB0aGUgZGF0YSBpcyBqb2luZWQgYXMgZXhwZWN0ZWQNCg0KVFRNIDwtDQogIGxlZnRfam9pbihUVE0sIA0KICAgICAgICAgICAgZ2VvbWV0cnlfdG9fam9pbiwNCiAgICAgICAgICAgIGJ5ID0gInRvX2lkIikNCg0KDQojIGpvaW4gdGhlIHN0YXRpb24gbmFtZSBvZiB0aGUgb3JpZ2luIHN0YXRpb24NCg0KbmFtZXNfdG9fam9pbiA8LSBzZWxlY3Qoc3RvcHMscGFyZW50X3N0YXRpb25fbmFtZSxpZCkgIyBkcm9wIHVubmVjZXNzYXJ5IGNvbHMNCm5hbWVzX3RvX2pvaW4gPC0gc3RfZHJvcF9nZW9tZXRyeShuYW1lc190b19qb2luKSAjIGRyb3AgZ2VvbWV0cnkNCm5hbWVzX3RvX2pvaW4gPC0gcmVuYW1lKG5hbWVzX3RvX2pvaW4sZnJvbV9pZCA9ICJpZCIpICMgcmVuYW1lICJpZCIgdG8gImZyb21faWQiIHNvIHRoYXQgdGhlIGRhdGEgaXMgam9pbmVkIGFzIGV4cGVjdGVkDQoNCg0KVFRNIDwtDQogIGxlZnRfam9pbihUVE0sIA0KICAgICAgICAgICAgbmFtZXNfdG9fam9pbiwNCiAgICAgICAgICAgIGJ5ID0gImZyb21faWQiKQ0KDQpUVE0gPC0gc3RfYXNfc2YoVFRNKQ0KDQpUVE0NCmBgYA0KDQpUaGlzIGZvcm1hdCBpcyBlYXNpZXIgdG8gaW50ZXJwcmV0IGlmIHdlIHRha2UgYSBzaW5nbGUgc3RvcC4gRm9yIGV4YW1wbGUsIGxldHMgZmluZCBhbGwgc3RvcHMgdGhhdCBjYW4gYmUgcmVhY2hlZCBmcm9tIFRoZXJlc2llbnN0cmHDn2Ugd2l0aGluIGEgbWVkaWFuIHRyYXZlbCB0aW1lIG9mIDIwIG1pbnV0ZXMuDQoNCmBgYHtyfQ0KdHNfc3RvcHNfMjAgPC0gZmlsdGVyKFRUTSxwYXJlbnRfc3RhdGlvbl9uYW1lID09ICJUaGVyZXNpZW5zdHJhw59lIiwgdHJhdmVsX3RpbWVfcDUwIDw9IDIwKQ0KDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKSsNCiAgZ2VvbV9zZihkYXRhID0gdHNfc3RvcHNfMjAsIGNvbG9yID0gImxpZ2h0Ymx1ZSIpKw0KICBnZW9tX3NmKGRhdGEgPSBmaWx0ZXIoc3RvcHMscGFyZW50X3N0YXRpb25fbmFtZSA9PSAiVGhlcmVzaWVuc3RyYcOfZSIpLCBjb2xvciA9ICJibHVlIikNCmBgYA0KDQpMZXQncyBleHBvcnQgdGhpcyBzbyB0aGF0IHdlIGNhbiB3b3JrIHdpdGggaXQgaW4gUUdJUy4NCg0KYGBge3J9DQpzdF93cml0ZShUVE0sIi4vb3V0cHV0L1RUTS5ncGtnIixhcHBlbmQgPSBGKQ0KYGBgDQpXZSBjYW4gYWNoaWV2ZSB0aGUgc2FtZSByZXN1bHQgYXMgYWJvdmUgYnkgZG9pbmcgdGhlIGZvbGxvd2luZzoNCg0KIVtdKC4vaW1nL3FnaXNfdHRtLnBuZykNCg0KDQpUaGVuLCBieSB1c2luZyB3aGF0IHlvdSd2ZSBsZWFybmVkIGluIHByZXZpb3VzIHR1dG9yaWFscywgeW91IGNhbiBjcmVhdGUgc2VydmljZSBhcmVhczoNCg0KIVtdKC4vaW1nL3NlcnZpY2VfYXJlYV9leGFtcGxlLnBuZykNCg0KDQoNCg0K